// Include standard headers
#include <fstream>
#include <iostream>
#include <math.h>
using namespace std;

// Include GLEW
#include <GL/glew.h>

// Include GLFW
#include <GL/glfw.h>

// Include GLUT
#include <GL/glut.h>

// Include GLM
#include <glm/glm.hpp>
using namespace glm;

#include "shader.hpp"

#ifndef GAMEENGINE_HPP
#define GAMEENGINE_HPP

class ColisionHandler{
public:
    ColisionHandler();

    virtual void colision(GLfloat x1, GLfloat y1,
                          GLfloat x2, GLfloat y2,
                          unsigned int position,
                          GLfloat *actingForceAtPosition,
                          GLfloat *worldPositions,
                          unsigned int *worldPositionsSize,
                          bool *drawPosiotion);
};

class LevelColisionHandler : public ColisionHandler{
public:

    void colision(GLfloat x1, GLfloat y1,
                          GLfloat x2, GLfloat y2,
                          unsigned int position,
                          GLfloat *actingForceAtPosition,
                          GLfloat *worldPositions,
                          unsigned int *worldPositionsSize,
                          bool *drawPosiotion);
};

class PaddleColisionHandler : public ColisionHandler{
public:

    void colision(GLfloat x1, GLfloat y1,
                          GLfloat x2, GLfloat y2,
                          unsigned int position,
                          GLfloat *actingForceAtPosition,
                          GLfloat *worldPositions,
                          unsigned int *worldPositionsSize,
                          bool *drawPosiotion);
};

class Matter{
private:
    string GLdraw;
    string matterPath;

    unsigned int matterClass;

    GLfloat *gVertexBufferData;
    unsigned int gVertexBufferDataSize;

    GLfloat **gColorBufferData;
    unsigned int gColorBufferDataSize;
    unsigned int *colorAtPosiotion;

    GLfloat *worldPositions;
    unsigned int worldPositionsSize;
    bool *drawPosiotion;

    bool *matterAtPositionMoved;

    GLfloat *actingForceAtPosition;

    ColisionHandler *ch;

    GLfloat *map;
    unsigned int mapSize;

    bool loadMatter(string matterPath);

public:
    Matter(string matterPath, ColisionHandler *ch);

    void moveMatternAtPosition(unsigned int position, float x, float y);
    void setForceAtPosition(unsigned int position, float x, float y);

    void colision(GLfloat x1, GLfloat y1,
                  GLfloat x2, GLfloat y2,
                  unsigned int position);

    //getters
    GLfloat *getVertexBufferData();
    unsigned int getVertexBufferDataSize();

    GLfloat **getColorBufferData();
    unsigned int getColorBufferDataSize();
    unsigned int getColorAtPosition(int position);

    GLfloat *getWorldPositions();
    unsigned int getWorldPositionsSize();
    bool *getDrawPosition();

    bool *getMatterAtPositionMoved();

    GLfloat *getActingForceAtPosition();

    GLfloat *getMap();
    unsigned int getMapSize();

    unsigned int getMatterClass();

    string getGLdraw();
};

class KeyHandler{
public:
    int key;
    Matter *matter;

    KeyHandler(int key, Matter *matter);
    KeyHandler();

    virtual void keyPress();
};

class MovePaddle : public KeyHandler{
private:
    int leftKey, rightKey, spaceKey;
    Matter *paddle, *ball;

    bool addForce;
    bool realaseBall;
public:
    MovePaddle(int leftKey, int rightKey, int spaceKey, Matter *paddle, Matter *ball);
    void keyPress();
};

class test : public KeyHandler{
private:
    int leftKey, rightKey, upKey, downKey;
    Matter *ball;
public:
    test(int leftKey, int rightKey, int upKey, int downKey, Matter *ball);
    void keyPress();
};

class Game{
private:
    Matter **matter;
    unsigned int matterNumber;

    KeyHandler **keysHandlers;
    unsigned int keysHandlersNumber;

public:
    Game();

    void reload();

    //getters
    Matter **getMatter();
    unsigned int getMatterNumber();

    KeyHandler **getKeysHandlers();
    unsigned int getKeysHandlersNumber();
};

class GamePhysicEngine {
private:
    Matter **matter;
    unsigned int matterNumber;

    void forceMoveMatter();
    void detectColision();

    bool pointToLineColisionDetection(GLfloat ax, GLfloat ay,
                                                        GLfloat bx, GLfloat by,
                                                        GLfloat cx, GLfloat cy,
                                                        GLfloat dx, GLfloat dy);

    bool objectToObjectColisionDetection(Matter **matter, unsigned int matterNumber,
                                         unsigned int matI, unsigned int posJ,
                                         unsigned int matN, unsigned int posM,
                                         GLfloat *cx, GLfloat *cy,
                                         GLfloat *dx, GLfloat *dy);
public:
    GamePhysicEngine(Matter **matter, unsigned int matterNumber);

    void physicEngineLoop();
};

class GameGraphicEngine {
private:
    Matter **matter;
    unsigned int matterNumber;

    GLuint *GPUVertexBuffers;
    GLuint **GPUColorsBuffers;

    GLuint programID;
    GLuint VertexArrayID;

    int initWinodw();
    void compileShaders();
    void loadMatterToGPU();
public:
    GameGraphicEngine(Matter **matter, unsigned int matterNumber);

    void graphicEngineLoop();

    void destroyWindow();

    void reload();
};

class GameEngine {
private:
    Game *game;
    GamePhysicEngine *gpe;
    GameGraphicEngine *gge;

    bool loop;

    int fpsCount;
    int fpsCountNum;
    int sleepTime;
    float *fps, fpsLimit;

    int frameCount;
    long int currentTime, previousTime;

    void calculateFPS();
    void limitFPS();

public:
    GameEngine();

    void startLoop();
};

#endif
